<?php
// +----------------------------------------------------------------------
// | Author: weifashi <605403358@qq.com>
// +----------------------------------------------------------------------
namespace soft\controls;
use think\facade\Session;
use think\Db;
use soft\Methods;

class FormService {
	
	//基础定义
	private $userInfo;//用户信息
	private $param;//用户信息
	private $formNode;//节点
	private $allFieldAttr = array(); // 所有字段属性
	private $erreoInfo; // 状态信息,
	private $stateInfo = array(
        '-1' => "找不到form节点！",
        '-2' => "创建视图失败，请检测sql语句是否正确！",
        '-3' => "查询错误，sql只能配置查询动作！",
        '-4' => "请配置表主键！",
        '-5' => "请配置xml的entity属性！",
        '-6' => "请传入oid参数！",
        '-7' => "请传入mode参数！",
    );
	
		
	/**
    * 构造函数自动执行
    * @return null
    */
    public function __construct($xml='',$id=''){
    	$this->userInfo = request()->userInfo;
    	$this->param = $param = request()->param();
    	$nodeId = $id ?: $param['id'] ?: '';
    	$xmlFile = Methods::getXmlFile($xml);
    	if(!$this->formNode = self::isExistNode($xmlFile,$nodeId)){
    		throw new \think\Exception('Form-找不到xml对应节点，或xml文件格式错误！');
    	}
    }	

	 /**
     * 获取示例
     * @param array $options 实例配置
     * @return static
     */
    protected static $instance = null;
    public static function instance($p1=null,$p2=null,$p3=null)
    {
        if (is_null(self::$instance)) {
            self::$instance = new self($p1,$p2,$p3);
        }
        return self::$instance;
    }
	
	//验证
	private function validation($sql,$entity,$key,$oid,$mode){
		$flag = true;
		if( !self::isExistDangerous($sql) ){
			$this->erreoInfo =  $this -> stateInfo[-3];
			$flag = false; 
		}else if(!$key || $key==''){
			$this->erreoInfo =  $this -> stateInfo[-4];
			$flag = false; 
		}else if(!$entity || $entity==''){
			$this->erreoInfo =  $this -> stateInfo[-5];
			$flag = false; 
		}else if(!$oid && $oid!=''){
			$this->erreoInfo =  $this -> stateInfo[-6];
			$flag = false; 
		}else if(!$mode && $oid!=''){
			$this->erreoInfo =  $this -> stateInfo[-7];
			$flag = false; 
		}
		return $flag;
	}
	
	/**
    * 获取form选项配置
    * /admin/Api/Grid/getOption
    * @access public
    * @param string $id   			
    * @param string $xml 		
    * @return array
    */
	public function getOption(){
		$data = array();
		$oid = Methods::getParam('oid');
		$mode = Methods::getParam('mode') ?: 1;
		$emptyFields = array();
		$formNode = $this->formNode;
		$sql = $formNode -> sql;
		$interface = $formNode ->sql-> interface['url'].'';
        
		$key = $sql->attributes()['key'];
		$entity= $sql->attributes()['entity'];
		
		//验证必填参数
		if( !self::validation($sql,$entity,$key,$oid,$mode) ){
			throw new \think\Exception($this->erreoInfo);
			return false;
		}
		
        //获取字段信息
        $fieldInfo = trim($sql.'') ? self::getFieldInfo($sql) : [];
        //获取节点数据
        $nodeObj = json_decode( json_encode($formNode,JSON_UNESCAPED_UNICODE) ,true);
        
		//转化xml数据格式
	    if($interface){
            $dbData = Methods::callApi($interface); 
            if($mode==1 && $dbData){  $mode = 2; }
            $data = self::getXmlData($nodeObj, $sql, [$dbData], $fieldInfo,$mode );
        }else{
            $dbData = self::getSqlValue($sql,$key,$oid,$mode); 
            $data = self::getXmlData($nodeObj, $sql, $dbData, $fieldInfo, $mode);
        }
        
        //转数据
        $data = self::replaceJoinExp($data);
  
		//验证是否存在空值和重复字段
        if($formNode ->sql['notValidSql'].'' !== 'true' && trim($sql.'') ){
            $emptyFields = self::getEmptyFields($data,$fieldInfo);
            $uniqueFields = self::getUniqueFields($data);
            if($emptyFields){
            	throw new \think\Exception("找不到 {$emptyFields} 字段！");
            	return false;
            }
            if($uniqueFields){
            	throw new \think\Exception("出现重复字段 {$uniqueFields} ！");
                return false;
            }
        }
        
		//返回错误或者配置数据
		return $data;
	}
	
	/**
    * 保存数据
    * /admin/Api/Grid/getOption
    * @access public
    * @param obj $param   			
    * @param xml $formNode 		
    * @return array
    */
	public function saveData(){
		$param = json_decode( $this->param["data"],true);
		$formNode = $this->formNode;
		$oid = Methods::getParam('oid');
		$mode = Methods::getParam('mode') ?: 1;
		$sql = $formNode -> sql;
		$key = $sql->attributes()['key'];
		$entity= $sql->attributes()['entity'];
		
		//验证必填参数
		if( !self::validation(true,$entity,$key,$oid,$mode) ){
			throw new \think\Exception($this->erreoInfo);
			return false;
		}
		
		//保存
		$dataKey = [];
		$saveData =  $this->getSaveField($formNode,$param['data'],$entity,$key);
		foreach($saveData as $dk => $ds){
			$entityName = $ds['entity'];
			$keyName = $ds['key'];
			foreach($ds['data'] as  $field => $val){
				$title = $this->allFieldAttr["$field"]['title'].'';
				$unique = $this->allFieldAttr["$field"]['unique'].'';
				$errormsg = $this->allFieldAttr["$field"]['errormsg'].'';
				$ignore = $this->allFieldAttr["$field"]['ignore'].'';
				//转码
				$ds['data']["$field"]= htmlspecialchars($val);
				//验证唯一
				if($ignore !="true" || ($ignore == "true" && !empty($val) ) ){
					if( strstr($unique,"/") && !Methods::callApi($unique) ){
						throw new \think\Exception($errormsg ? "$errormsg" : "【 $title 】 不能添加重复值！");
						return false;	
					}else if( $unique =='true' && !strstr($unique,"/") ){
						$map = []; $map2 = [];
						$map["$field"] = $val;
						if( $mode != "1" ){
							$map2[] = array("$keyName",'neq',"$oid");
						}
						if( \Db::table($entityName) ->where($map) ->where($map2) -> find() ){
							throw new \think\Exception($errormsg ? "$errormsg" : "【 $title 】 不能添加重复值！");
							return false;	
						}
					}
				}
			}
			//子表主键赋值
			if($dk>0 && count($dataKey) ){
				$ds['data']["{$ds['key']}"] = $dataKey[0];
			}
			//保存数据
			if(!$this->erreoInfo){
				if($mode == 1){
					$dataKey[] = \Db::table($entityName)->insertGetId($ds['data']);
				}else{
					\Db::table($entityName)->where([["$keyName",'=',"$oid"]])->update($ds['data']);
					$dataKey[] = intval("$oid");
				}
			}
		}
		
		return [ 'key'=>$dataKey[0] ];
	}
	
	/**
    * 获取xml数据
    * /admin/Api/Grid/getOption
    * @access private
    * @param obj $nodeData   			
    * @param xml $sqlNode 		
    * @param array $dbData 		
    * @param array $fieldInfo 		
    * @param string $mode 		
    * @return array
    */
	private function getXmlData($nodeData,$sqlNode,$dbData,$fieldInfo,$mode){
		$data = [];
		$dataUrlNames = ['select','multiSelect','radio','checkbox'];
		$type = $nodeData['@attributes']['type'];
		$dataUrl = $nodeData['@attributes']['dataUrl'];
		$isGetOption = in_array($type,$dataUrlNames) && $dataUrl;
		foreach($nodeData as $key => $ds ){
			$isObjType =  in_array($key,['buttons','sql','tabs','checkbox']);
			$isFilterTags =  in_array($key,['sql','saves','save']);
			switch($key){
			    case '@attributes':
					$field = $ds['field'];
			        foreach($ds as $k => $d ){
			        	if( $field && $k == 'value' && $mode != 1){
							$rxp_val =  self::getValue($dbData,$field,$ds,$sqlNode);
						}else{
							$rxp_val = self::replaceExp($d,$ds,$sqlNode,$dbData);
						}
			        	if( $field && $k == 'value' ){
			        		$data["maxLength"] = self::getMaxLength($fieldInfo,$field);
							$data["join_field"] = $field.'=>'.$rxp_val;
							$data["value"] =  $rxp_val;
						}
						$data[$k] = $rxp_val; 
					}
					if($field && $mode==3 ){
						$data["edit"] = "false";
					}
					if( in_array( $type, $dataUrlNames) ){
						$data['option'] = [];
						if($nodeData['option'] && $nodeData['option']['@attributes']){
							$arr = [];
							foreach($nodeData['option']['@attributes'] as  $k => $d ){
								$arr[$k] = $d;
	    					}
							array_push($data["option"],$arr);
						}
						if(!empty($dataUrl.'')){
							$dataUrl =  self::replaceExp($dataUrl,$ds,$sqlNode,$dbData);
							foreach(Methods::callApi( $dataUrl ) as  $h ){
	    						array_push($data["option"],$h);
	    					}
						}
					}
			       break;
				case $ds[0] &&  !$isGetOption  && !$isFilterTags ? $key : false: //递归
					foreach($ds as $k => $d ){
						$data[$key][] = self::getXmlData($d,$sqlNode,$dbData,$fieldInfo,$mode);
					}
			        break;
				case !$isGetOption && !$isObjType && !$isFilterTags ?  $key : false: //递归
					$data[$key][] = self::getXmlData($ds,$sqlNode,$dbData,$fieldInfo,$mode);
			       	break;
				case !$isGetOption  && !$isFilterTags ?  $key : false: //递归
					$data[$key] = self::getXmlData($ds,$sqlNode,$dbData,$fieldInfo,$mode);
			       	break;   	         
			    default:
			}
		}
		return $data;
	}
	
	/**
    * 替换Join占位符
    * /admin/Api/Grid/getOption
    * @access private
    * @param array $data 		
    * @return array
    */
	private function replaceJoinExp($data){
		$dataStr = json_encode($data,JSON_UNESCAPED_UNICODE);
		preg_match_all( '/(?<="join_field":")[^"]+/',$dataStr,$match);
		preg_match_all( '/(?<={{join:)[^}}]+/',$dataStr,$match2);
		foreach($match[0] as  $ds ){
			$dss = explode("=>", $ds);
			foreach($match2[0] as  $join ){
				if( $join == $dss[0] ){
					$dataStr = preg_replace("/{{join:{$join}}}/",$dss[1],$dataStr);
				}
			}
		}
		$dataStr = preg_replace("/{{.*?}}/",'',$dataStr);
		$dataStr = preg_replace('/,"join_field":".*?",/',',',$dataStr);
		return json_decode($dataStr,true);
	}
	
	/**
    * 替换占位符
    * /admin/Api/Grid/getOption
    * @access private
    * @param array $data 		
    * @return array
    */
	private function replaceExp($src,$attr,$sqlNode,$dbData){
		$src = Methods::replaceExp($src,$attr);
		preg_match_all( '/(?<={{)[^}}]+/',$src,$match);
		foreach( $match[0] as $val ){
			$v = "{{{$val}}}";
			switch ($val){
				case strstr($val,"++") ? true : false: //自增
					$entity = $sqlNode['entity'];
					$digit = str_replace("base:","",$val);
					$digit = str_replace("++","",$digit);
				  	$d = \Db::table("$entity") -> field("max({$attr['field']} + 0) as max") ->select();
					$v = $d[0]["max"] ? $d[0]["max"] + 1 : 1;
					if($digit){ $v = sprintf("%0{$digit}d", $v); }
					$val = str_replace("++","kk",$val);
					$src = str_replace("++","kk",$src);
				  	break;
				case strstr($val,"field:") ? true : false: //字段
					$field = str_replace("field:","",$val);
				  	$v =  self::getValue($dbData,$field,$attr,$sqlNode);
				  	break;
			}
			$src = preg_replace("{{{".$val."}}}", $v ,$src);
		}
		return  $src;
	}
	
	/**
    * 获取空字段
    * @access private
    */
	private function getEmptyFields($data,$formFields){
		$arr = [];
		preg_match_all( '/(?<="field":")[^"]+/',json_encode($data),$match);
		preg_match_all( '/(?<="column_name":")[^"]+/',json_encode($formFields),$match2);
		foreach($match[0] as  $field ){
			if(!in_array($field, $match2[0]) && $field !=""){ 
				$arr[]= $field;
			}
		}
		return implode(", ",$arr);
	}
	
	/**
    * 获取重复字段
    * @access private
    */
	private function getUniqueFields($data){
		$arr = [];
		preg_match_all( '/(?<="field":")[^"]+/',json_encode($data),$match);
		$unique_arr = array_unique($match[0]); 
		$repeat_arr = array_diff_assoc($match[0], $unique_arr); 
		return  implode(",",array_unique($repeat_arr));
	}
	
	/**
    * 获取字段属性
    * @access private
    */
	private function getFieldAttr($formNode,$field){
		$attr = [];
		foreach($formNode -> tabs ->tab as $tab){
			foreach($tab-> section as $section ){
    			foreach($section->row  as $row ){
	    			if($row -> attributes()["field"] == $field){
						$attr  = $row-> attributes();
	    			}
	    		}
    		}
		}
		return $attr;
	}
	
	/**
    * 获取保存字段
    * @access private
    */
	private function getSaveField($formNode,$saveData,$entity,$key){
		$key = strstr( $key.'' ,".") ? strtr( strstr( $key.'' ,".") ,"."," ") : $key.'';
		$needSave = explode(",", $formNode -> sql->attributes()['needSave'] );
		$data = array();
		//获取所有已配置的可收集字段
		$allFields = [];
		foreach($formNode -> tabs ->tab as $tab){
			foreach($tab-> section as $section ){
    			foreach($section->row  as $row ){
    				$this->allFieldAttr["{$row['field']}"] = $row;
	    			if($row -> attributes()["needSave"] != 'false' && $row['field'].''){
						array_push($allFields, $row['field'].'' );
	    			}else{
	    				array_push($needSave, $row['field']);
	    			}
	    		}
    		}
		}
		foreach($formNode->saves->save as $d){
			$fields = explode(",", $d['field']);
			$allFields = array_merge( $allFields, $fields );
			array_push($data,array("entity"=>$d["entity"].'',"key"=>$d["key"].'','field'=>$fields ));
		}
		
		//获取可保存数据
		$preservable = [];
		foreach($saveData as $k => $d ){
			if( in_array($k, $allFields) ){
				if( $this->getFieldAttr($formNode,$k)['saveFormat'] == 'md5' ){
					$preservable[$k] =  md5($d);
				}else{
					$preservable[$k] = $d;
				}
			}
		}

		//装载到配置表中
		$keyData = [];
		foreach($preservable as $k => $v ){
			foreach($data as $ke=>$ds ){
				if( in_array($k, $ds['field']) || $k == $ds['key'] ){
					$data[$ke]['data'][$k] = $v;
				}
			}
			if( !in_array($k,$needSave) || $k == $key ){
				$keyData[$k] = $v;
			}
		}
		array_unshift($data, array("entity"=>$entity.'',"key"=>$key,'field'=>'',"data"=>$keyData));
		return $data;
	}
	
	/**
    * 获取值
    * @access private
    * @param string $field 行字段名称
    * @param array  $data xml sq语句查询出的数据
    * @param array $rows 索引行
    * @return string
    */
	private function getValue($dbData,$field,$attr,$sqlNode){
		$val = "";
		$type= $attr["type"];
		$format= $attr["format"];
		$editValue= $attr["editValue"] ?: $attr["editModeValue"];
		$isField=false;
		foreach($dbData as  $vs ){
			foreach($vs as $key => $v){
				if($field == $key){
					$isField = true;
					switch ($type){
						case "datetime":
					  		if($v && $v != "0000-00-00 00:00:00" && $format !='HH:mm' && $attr['range'] != 'true'){
								$format= Methods::getFormat($format);
								$val 	=  date_format(date_create("$v"),"$format");
							}else{
								$val = !$v ? "" : $v;
							}
					  		break;
						case "number":
							$val = !$v ? 0 : $v;
					  		break;
						default:
							$val = $v == null ? '' : $v; 	  	  
					}
				}
			}
		};
		if($editValue){ $val = self::replaceExp($editValue,$attr,$sqlNode,$data);}
		return ($isField ? $val : "");
	}
	
	/**
    * 获取长度
    * @access private
    * @param string $information  xml-sql中查询出的字段信息
    * @param string $field 			  指定的字段
    * @return string
    */
	private function getMaxLength($fieldInfo,$field){
		$maxLength = "";
		if($field){
			foreach($fieldInfo as $key => $arrs ){
				if($arrs['column_name'] == $field){
					$maxLength= $arrs['character_maximum_length'];
				}
			};
		}
		return $maxLength  ?  $maxLength : '';
	}
	
	
	/**
    * 监听xmlsql语句是否有除了查询以外的动作
    * @access private
    * @return string
    */
	private function isExistDangerous($sql){
		$sql = strtolower($sql);
		$sql = preg_replace('/\binsert\s|\bupdate\s|\bdelete\s|\bcreate\s|\balter\s|\bdrop\s|(\bexec|\bexecute)[\s\(]|\bsp_|\bxp_|\sinto\s/','error--error',$sql);
		if(strstr($sql,"error--error")){
			return false;
		}else{
			return $sql;
		}
	}
	
	/**
    * 获取数据库值
    * @access private
    * @param string $id 
    * @param string $xml 
    * @param string $mode
    * @param string $oid
    * @return  string
    */
	private function getSqlValue($sql,$key,$oid,$mode){
		$param = self::getUrlParam();
		$mode = $mode ? $mode : $param["mode"]; 
	    $oid = $mode == 1 ? 0 : $oid; 
		return  self::multiQuery(str_replace("1=1",explode(",",$key)[0]."='".$oid."'",$sql),$sql['showSql'].'');
	}
	
	private function getUrlParam(){
		$http = parse_url($_SERVER['HTTP_REFERER']);
		$query = $http["query"];
		parse_str($query,$parr);
		return $parr;
	}
	
	/**
    * 获取sql字段信息
    * @access private
    * @param string $id  		
    * @param string $xml 	
    * @return string
    */
	private function getFieldInfo($sql){
		//1.先创建视图 ,2.再获取视图中字段属性 ,3.然后删除视图
		$sqls = "
			create or replace view xw_form_view  as ($sql);

			select column_name,column_comment,data_type,character_maximum_length
			from information_schema.columns 
			where table_name='xw_form_view';

			drop view  xw_form_view;
		";
		return self::multiQuery($sqls); 
	}
	
	//多查询
	private function multiQuery($sqls="",$showSql=''){
		//替换店铺id	
        $shop_id = $this->userInfo['shop_id'] ?: 0;
		$sqls = str_replace("{{base:当前店铺}}", $shop_id ? "shop_id=$shop_id" : '10=10',$sqls); 	
		$sqls = str_replace("{{base:shop_id}}", $shop_id,$sqls); 	
		//是否显示sql
		if($showSql=='true') throw new \think\Exception($sqls);
		//查询数据
		$data=array();
		$c = config()['database'];
		$conn = new \mysqli( $c['hostname'], $c['username'],$c['password'],$c['database'],$c['hostport']);
		if (mysqli_connect_errno($con)){
			$this->erreoInfo = "连接到 MySQL 失败: " . mysqli_connect_error();
			throw new \think\Exception($this->erreoInfo);
		}else{
			$conn->query("set names utf8");
			if(mysqli_multi_query($conn, $sqls)) {
		        do {
		            if($result = mysqli_store_result($conn)) {
		                while( $row = mysqli_fetch_assoc($result)  ) {
		                	array_push($data, $row);
		                }
		                mysqli_free_result($result);        
		            }
		        } while(mysqli_next_result($conn));
		    }
			if($conn->error){
				$this->erreoInfo = 'sqlError:  ' . $conn->error;
				throw new \think\Exception($this->erreoInfo);
				mysqli_close($conn);
			}
		    mysqli_close($conn);
		}
		return $data;
	}
	
	
	/**
    * 是否存在grid节点
    * /admin/Api/Grid/getOption
    * @param string $xmlFile   			xml-id
    * @return  bu
    */
	private function isExistNode( $xmlFile,$id ){
		$node = false;
		foreach( $xmlFile->form as $val){
	    	if( $id == $val->attributes()["id"] ){
	    		$node = $val;
	    	}
		}
		return $node;
	}
	
	
}




